package com.kamistoat.meimeimall.meimeimallorder.listener;


import com.kamistoat.meimeimall.common.To.paymentTo.PayAsyncTo;
import com.kamistoat.meimeimall.common.constant.OrderConstant;
import com.kamistoat.meimeimall.common.dubboInterface.PaymentDubboInterface;
import com.kamistoat.meimeimall.common.dubboInterface.WareDubboInterface;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import com.kamistoat.meimeimall.common.utils.R;
import com.kamistoat.meimeimall.meimeimallorder.config.AlipayTemplate;
import com.kamistoat.meimeimall.meimeimallorder.localTCC.OrderServiceLocalTCC;
import com.kamistoat.meimeimall.meimeimallorder.service.OrderItemService;
import com.kamistoat.meimeimall.meimeimallorder.service.OrderService;
import com.rabbitmq.client.Channel;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.dubbo.config.annotation.Reference;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;

/**
 * 收到支付回调后，发送支付消息给消息队列。该监听器处理支付消息
 */
@Service
public class OrderPayMQListener {
    protected Logger logger = LoggerFactory.getLogger(OrderPayMQListener.class);

    @Autowired
    AlipayTemplate alipayTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    OrderService orderService;
    @Autowired
    OrderItemService orderItemService;
    @Autowired
    OrderServiceLocalTCC orderServiceLocalTCC;

    @Reference
    WareDubboInterface wareDubboInterface;
    @Reference
    PaymentDubboInterface paymentDubboInterface;

    /**
     * 监听到支付消息，推动订单状态流转
     */
    @RabbitListener(queues = "order.pay.callback.queue")
    @GlobalTransactional
    public void payCallbackMQListener(Message message, PayAsyncTo payAsyncTo, Channel channel) throws IOException {
        logger.info("监听到支付回调消息，开始推动状态流转");
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        // 要操作订单，需要给对应订单上排他锁。
        // 因为下单按钮做了排他锁，所以下单时操作订单不需要再加锁，但是此处的支付成功回调需要手动加上锁。
        RLock orderStatusModifyLock = redissonClient.getLock(OrderConstant.ORDER_MODIFY_LOCK_PREFIX + payAsyncTo.getOut_trade_no());
        // 此处为单个订单上锁，保证了一个订单只有一个实例在操作
        orderStatusModifyLock.lock();

        R handlePayCallbackR;

        try {
            /**
             * 1. 保存交易流水。
             * 分布式事务分支.
             */
            paymentDubboInterface.savePaymentInfo(null, payAsyncTo);

            /**
             * 如果支付回调中显示订单支付成功，则继续向下执行。如果是支付失败，则只记录流水即可
             */
            if (payAsyncTo.getTrade_status().equals("TRADE_SUCCESS") || payAsyncTo.getTrade_status().equals("TRADE_FINISHED")) {
                /**
                 * 2. 拆分订单。只有当支付宝返回支付成功时才会执行这一步，将总订单按照品牌拆分成子订单。总订单/子订单都保存在同一个数据库中
                 */
                orderServiceLocalTCC.savePayedSubOrder(null, payAsyncTo);

                /**
                 * 3. 从锁定的库存值中扣除，真正扣除库存
                 */
                R handleSuccessPayWareR = wareDubboInterface.handleSuccessPayWare(null, payAsyncTo.getOut_trade_no());
                if (handleSuccessPayWareR.getCode() != 0) {
                    throw new RRException(handleSuccessPayWareR.getMsg(), handleSuccessPayWareR.getCode());
                }
            }

            handlePayCallbackR = R.ok("支付动作处理完成").put("orderSn", payAsyncTo.getOut_trade_no());
            channel.basicAck(deliveryTag, false);
            rabbitTemplate.convertAndSend("order-event-exchange", "order.pay.result", handlePayCallbackR);

        } catch (Exception exception) {
            if (!(exception instanceof RRException)) {
                handlePayCallbackR = R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), "支付动作处理失败，前端请持续监听直到重试成功。失败原因:" + exception.getMessage())
                        .put("orderSn", payAsyncTo.getOut_trade_no());
            } else {
                handlePayCallbackR = R.error("支付动作处理失败，前端请持续监听直到重试成功。失败原因:" + exception.getMessage())
                        .put("orderSn", payAsyncTo.getOut_trade_no());
            }
            // 向下单结果队列发送消息
            rabbitTemplate.convertAndSend("order-event-exchange", "order.pay.result", handlePayCallbackR);
            // 如果失败，由消息队列持续重试。因为钱已经从第三方支付中扣了，这里就算我自己回滚，要退款也会很麻烦，所以必须重试。
            channel.basicReject(deliveryTag, true);
        } finally {
            orderStatusModifyLock.unlock();
        }
    }
}
